﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Golem.Indexer.Data;
using System.Threading.Tasks;
using System.Threading;
using System.Net.NetworkInformation;

namespace Golem.Indexer.Indexers
{
    internal abstract class Indexer : IIndexer
    {
        protected Dictionary<String, MachineDescriptor> activeMachines = new Dictionary<String, MachineDescriptor>();

        public bool IndexHidden { get; set; }
        public event IndexerFileHandler FilesIndexed;
        public event IndexerProtocolHandler ProtocolIndexingStarted;
        public event IndexerProtocolHandler ProtocolIndexingEnded;
        public event IndexerErrorHandler ProtocolIndexingErrorThrowed;
        public Protocol Protocol { get; private set; }

        private MachineDescriptor currentMachine = null;
        private bool quit = false;
        private bool running = false;

        private ManualResetEvent dataWait = new ManualResetEvent(false);

        public int RemainingMachinesCount { get { lock (this.activeMachines) { return this.activeMachines.Count; } } }

        protected void ThrowException(Exception ex)
        {
            if (this.ProtocolIndexingErrorThrowed != null)
                this.ProtocolIndexingErrorThrowed(this.currentMachine, this.Protocol, ex); 
        }

        public Indexer(Protocol protocol)
        {
            IndexHidden = false;
            this.Protocol = protocol;
        }

        protected void IndexFiles(MachineDescriptor machine, List<FileDescriptor> files)
        {
            if (this.FilesIndexed != null)
                this.FilesIndexed(machine, this.Protocol, files);
        }

        public void Add(params MachineDescriptor[] machines)
        {
            foreach (var machine in machines)
            {
                if (machine.IsOnline)
                {
                    lock (activeMachines)
                    {
                        activeMachines.Add(machine.IP, machine);
                        dataWait.Set();
                    }
                }
            }
        }

        public void Clear()
        {
            lock (this.activeMachines)
            {
                this.activeMachines.Clear();
            }
        }

        protected abstract void GetFiles(MachineDescriptor machine);

        public void RunIndexing()
        {
            if (running)
            {
                this.dataWait.Set();
                return;
            }

            Task.Factory.StartNew(() =>
                {
                    running = true;

                    while (!quit)
                    {
                        MachineDescriptor machine = null;
                        lock (this.activeMachines)
                        {
                            if (this.activeMachines.Count > 0)
                            {
                                var m = this.activeMachines.First();
                                this.activeMachines.Remove(m.Key);
                                machine = m.Value;
                            }
                        }

                        if (machine == null)
                        {
                            dataWait.Reset();
                            dataWait.WaitOne();
                            continue;
                        }

                        Task.Factory.StartNew(() => { ProceedMachine(machine); });
                    }

                    running = false;
                });
        }

        private void ProceedMachine(MachineDescriptor machine)
        {
            if (this.ProtocolIndexingStarted != null)
                this.ProtocolIndexingStarted(machine, this.Protocol);

            this.currentMachine = machine;
            GetFiles(machine);

            if (this.ProtocolIndexingEnded != null)
                this.ProtocolIndexingEnded(machine, this.Protocol);
        }

        public void StopIndexing()
        {
            this.quit = true;
            dataWait.Set();
        }

    }
}
